package top.chendawei.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.Assert;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.chendawei.aop.exceptions.CommonPartsExceptions;
import top.chendawei.system.dao.HibernateDaoSupport;
import top.chendawei.system.enums.CommomOperator;
import top.chendawei.system.service.ICommonService;
import top.chendawei.util.JsonResult;

import javax.persistence.Id;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

@Service("commonService")
public class CommonServiceImpl extends HibernateDaoSupport implements ICommonService {

    @Override
    @Transactional
    public <T> JsonResult<T> search(List<String> attrNames, List<Object> attrValues, List<CommomOperator> operators, Class<T> type,
                                    Integer currentPage, Integer pageSize, String orderBy, String orderType) {
        JsonResult<T> json = new JsonResult();
        json.setPageSize(pageSize);
        json.setCurrent(currentPage);

        StringBuilder hql = new StringBuilder();
        hql.append("from " + type.getSimpleName() + " where 1=1 ");
        //收集参数拼装hql
        collectHqlByParam(hql, attrNames, attrValues, operators);
        StringBuilder selcetSql=new StringBuilder(hql);
        if (!StringUtils.isEmpty(orderBy)) {
            selcetSql.append(" order by " + orderBy + " " + orderType);
        }
        Query query = getCurrentSession().createQuery(selcetSql.toString());
        //hql中参数赋值（规则和拼装的hql一致）
        collectSetParam(query, attrNames, attrValues, operators);
        if (null != currentPage && null != pageSize && pageSize > 0) {
            int from = (currentPage - 1) * pageSize;
            query.setFirstResult(from);
            query.setMaxResults(pageSize);
        }
        List<T> list = query.list();
        json.setRecords(list);
        if (CollectionUtils.isNotEmpty(list) && null != pageSize && pageSize > 0) {
            query = getCurrentSession().createQuery("select count(*) " + hql);
            //hql中参数赋值（规则和拼装的hql一致）
            collectSetParam(query, attrNames, attrValues, operators);
            int totalCount = ((Number) query.iterate().next()).intValue();
            int totalPage = totalCount % pageSize == 0 ? totalCount / pageSize : totalCount / pageSize + 1;
            json.setTotal(totalCount);
            json.setTotalPage(totalPage);
        }
        return json;
    }

    private void collectSetParam(Query query, List<String> attrNames, List<Object> attrValues, List<CommomOperator> operators) {
        for (int i = 0; i < operators.size() && i < attrNames.size(); i++) {
            String val = attrValues.get(i) == null ? "" : attrValues.get(i).toString().trim();
            CommomOperator operator = operators.get(i);
            switch (operator) {
                case equals: {
                    query.setParameter("param" + i, attrValues.get(i));
                    break;
                }
                case in: {
                    if (attrValues.get(i) instanceof Collection) {
                        Collection c = (Collection) attrValues.get(i);
                        if (CollectionUtils.isNotEmpty(c)) {
                            query.setParameter("param" + i, attrValues.get(i));
                        }
                    } else {
                        throw new RuntimeException("fail param must be collection ");
                    }
                    break;
                }
                case like: {
                    query.setParameter("param" + i, "%" + val + "%");
                    break;
                }
                case rightLike: {
                    query.setParameter("param" + i, val + "%");
                    break;
                }
                case gt: {
                    query.setParameter("param" + i, val);
                    break;
                }
                case lt: {
                    query.setParameter("param" + i, val);
                    break;
                }
                case ge: {
                    query.setParameter("param" + i, val);
                    break;
                }
                case le: {
                    query.setParameter("param" + i, val);
                    break;
                }
                case or: {
                    if (attrValues.get(i) instanceof Collection) {
                        Collection c = (Collection) attrValues.get(i);
                        if (CollectionUtils.isNotEmpty(c)) {
                            Iterator it = c.iterator();
                            int j = 0;
                            while (it.hasNext()) {
                                query.setParameter("param" + i + j, it.next());
                                j++;
                            }
                        }
                    } else if (StringUtils.isNoneBlank(val)) {
                        String[] vals = val.split("\\|\\|");
                        for (int j = 0; j < vals.length; j++) {
                            query.setParameter("param" + i + j, vals[j]);
                        }
                        break;
                    } else {
                        throw new RuntimeException("fail or param must be collection or with || split str");
                    }
                    break;
                }
                case isNotNull: {
                    break;
                }
                case isNull: {
                    break;
                }
                case str: {
                }
                default: {
                    throw new RuntimeException("fail enum");
                }
            }
        }
    }


    private void collectHqlByParam(StringBuilder hql, List<String> attrNames, List<Object> attrValues, List<CommomOperator> operators) {
        for (int i = 0; i < operators.size() && i < attrNames.size(); i++) {
            CommomOperator operator = operators.get(i);
            Assert.notEmpty(attrNames.get(i));
            Assert.notNull(operator);
            String val = attrValues.get(i) == null ? "" : attrValues.get(i).toString();
            switch (operator) {
                case equals: {
                    hql.append(" and " + attrNames.get(i) + "=:param" + i);
                    break;
                }
                case in: {
                    if (attrValues.get(i) instanceof Collection) {
                        Collection c = (Collection) attrValues.get(i);
                        if (CollectionUtils.isNotEmpty(c)) {
                            hql.append(" and " + attrNames.get(i) + " in (:param" + i + ")");
                        } else {
                            hql.append(" and " + attrNames.get(i) + " is null ");
                        }
                    } else {
                        throw new RuntimeException("fail param must be collection ");
                    }
                    break;
                }
                case like: {
                    hql.append(" and " + attrNames.get(i) + " like :param" + i);
                    break;
                }
                case rightLike: {
                    hql.append(" and " + attrNames.get(i) + " like :param" + i);
                    break;
                }
                case gt: {
                    hql.append(" and " + attrNames.get(i) + " >:param" + i);
                    break;
                }
                case lt: {
                    hql.append(" and " + attrNames.get(i) + " <:param" + i);
                    break;
                }
                case ge: {
                    hql.append(" and " + attrNames.get(i) + " >=:param" + i);
                    break;
                }
                case le: {
                    hql.append(" and " + attrNames.get(i) + " <=:param" + i);
                    break;
                }
                case or: {
                    if (attrValues.get(i) instanceof Collection) {
                        Collection c = (Collection) attrValues.get(i);
                        if (CollectionUtils.isNotEmpty(c)) {
                            hql.append(" and (");
                            for (int j = 0; j < c.size(); j++) {
                                hql.append((j == 0 ? "" : " or ") + attrNames.get(i) + "=:param" + i + j);
                            }
                            hql.append(") ");
                        }
                    } else if (StringUtils.isNoneBlank(val)) {
                        String[] vals = val.split("\\|\\|");
                        hql.append(" and (");
                        for (int j = 0; j < vals.length; j++) {
                            hql.append((j == 0 ? "" : " or ") + attrNames.get(i) + " =:param" + i + j);
                        }
                        hql.append(") ");
                        break;
                    } else {
                        throw new RuntimeException("fail or param must be collection or with || split str");
                    }
                    break;
                }
                case isNotNull: {
                    hql.append(" and " + attrNames.get(i) + " is not null ");
                    break;
                }
                case isNull: {
                    hql.append(" and " + attrNames.get(i) + " is  null ");
                    break;
                }
                case str: {
                    hql.append(" and " + attrNames.get(i));
                }
                default: {
                    throw new RuntimeException("fail enum");
                }
            }
        }
    }

    @Override
    @Transactional
    public <T> T commonFind(Class<T> type, long id) {
        T t = (T) getCurrentSession().get(type, Long.valueOf(id));
        return t;
    }

    @Override
    @Transactional
    public <T> List<T> search(String attr, Object value, Class<T> type) {
        if (StringUtils.isBlank(attr) || null == value) {
            return Collections.EMPTY_LIST;
        }
        return this.findByAttributeList(type, attr, value);
    }

    @Override
    @Transactional
    public <T> List<T> search(List<String> attrNames, List<Object> attrValues, List<CommomOperator> operators, Class<T> paramClass) {
        return this.search(attrNames, attrValues, operators, paramClass, null, null);
    }

    @Override
    @Transactional
    public <T> List<T> search(List<String> attrs, List<Object> values, List<CommomOperator> operators, Class<T> paramClass,
                              String orderBy, String orderType) {
        if (CollectionUtils.isEmpty(attrs) || CollectionUtils.isEmpty(values) || CollectionUtils.isEmpty(operators)) {
            return Collections.EMPTY_LIST;
        }
        if (attrs.size() != values.size() && attrs.size() == operators.size()) {
            throw new RuntimeException("参数异常");
        }

        JsonResult<T> json = this.search(attrs, values, operators,
                paramClass, null, 0, null, null);
        if (null != json) {
            return json.getRecords();
        }
        return Collections.EMPTY_LIST;
    }

    @Override
    @Transactional
    public <T> T findByAttribute(Class<T> type, String attrName, Object val) {
        List<T> list = this.findByAttributeList(type, attrName, val);
        if (list.size() > 0) {
            return list.get(0);
        }
        return null;
    }

    @Override
    @Transactional
    public <T> T findOneByAttribute(Class<T> type, String attrName, Object val) {
        List<T> list = this.findByAttributeList(type, attrName, val);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        if (list.size() == 1) {
            return list.get(0);
        }
        throw new RuntimeException("findOneByAttribute  more than one result");
    }

    @Override
    @Transactional
    public <T> List<T> findByAttributeList(Class<T> type, String attr, Object value) {
        if (StringUtils.isBlank(attr) || null == value) {
            return Collections.EMPTY_LIST;
        }
        if (value instanceof Collection) {
            return this.search(Arrays.asList(attr), Arrays.asList(value), Arrays.asList(CommomOperator.in), type);
        } else {
            return this.search(Arrays.asList(attr), Arrays.asList(value), Arrays.asList(CommomOperator.equals), type);
        }
    }

    @Override
    @Transactional
    public <T> boolean commonDelete(Class<T> type, Long... ids) {
        for (int i = 0; i < ids.length; i++) {
            Query query = getCurrentSession().createQuery("delete  " + type.getSimpleName() + " as t where t.id=:id");
            query.setParameter("id", ids[i].longValue());
            query.executeUpdate();
        }
        return true;
    }

    @Override
    @Transactional
    public <T> void commonAdd(T obj) {
        Long id = (Long) getCurrentSession().save(obj);
        Class<? extends Object> clz = obj.getClass();
        Field[] proDescrtptors = clz.getFields();
        if (proDescrtptors != null && proDescrtptors.length > 0) {
            for (Field propDesc : proDescrtptors) {
                if (propDesc.isAnnotationPresent(Id.class)) {
                    try {
                        PropertyDescriptor p = new PropertyDescriptor(propDesc.getName(), clz);
                        Method setMethod = p.getWriteMethod();
                        setMethod.invoke(obj, id);
                        break;
                    } catch (Exception e) {
                        throw new CommonPartsExceptions("保存失败");
                    }
                }
            }
        }
    }

    @Override
    @Transactional
    public <T> void commonBatchAdd(List<T> ll) {
        ll.forEach(x -> {
            getCurrentSession().save(x);
        });
    }

    @Override
    @Transactional
    public <T> void commonUpdate(T obj) {
        getCurrentSession().update(obj);
    }

    @Override
    @Transactional
    public <T> void commonSaveOrUpdate(T obj, Long id) {
        T t = (T) getCurrentSession().get(obj.getClass(), Long.valueOf(id));
        //复制旧的属性过来，忽略null属性，忽略null值，有值的以新的为主，null的则以旧为主
        BeanUtil.copyProperties(obj, t, true, CopyOptions.create().setIgnoreNullValue(true).setIgnoreError(true));
        getCurrentSession().update(t);
    }

    private String genColum(String str) {
        return str.replaceAll("[A-Z]", "_$0").toLowerCase();
    }
}
